Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit 08f98b987763016456bb62f1b5271615b50104ef


Parents : ca93677
Author : Ivan <ivan@quad4.io>
Signature : Invalid signer <e46112d44649266d71fe2193e00a4710>, author is <ivan@quad4.io>
Date : 2026-07-09T10:59:58-05:00

feat(self-check): add new plugin runtime checks and enhance self-test diagnostics for plugins and sideband configurations

Changes
Diff

diff --git a/meshchatx/meshchat.py b/meshchatx/meshchat.py
index f369a7be..d58dddf4 100644
--- a/meshchatx/meshchat.py
+++ b/meshchatx/meshchat.py
@@ -1044,6 +1044,7 @@ class ReticulumMeshChat:
unicode_result = self_check_mod.check_unicode_path(storage_base)
rnode_result = self_check_mod.check_rnode_support()
bot_launcher_result = self_check_mod.check_bot_launcher()
+ plugins_runtime_result = self_check_mod.check_plugins_runtime(self)
web_results = self_check_mod.check_web_stack(self)
return {
@@ -1114,6 +1115,29 @@ class ReticulumMeshChat:
"http_telephone_good": web_results.get(
"http_telephone_good", {"status": "failed", "reason": "missing"}
),
+ "http_plugins_good": web_results.get(
+ "http_plugins_good", {"status": "failed", "reason": "missing"}
+ ),
+ "http_plugins_trust_good": web_results.get(
+ "http_plugins_trust_good",
+ {"status": "failed", "reason": "missing"},
+ ),
+ "http_sideband_plugins_good": web_results.get(
+ "http_sideband_plugins_good",
+ {"status": "failed", "reason": "missing"},
+ ),
+ "http_sideband_config_good": web_results.get(
+ "http_sideband_config_good",
+ {"status": "failed", "reason": "missing"},
+ ),
+ "http_rrc_hubs_good": web_results.get(
+ "http_rrc_hubs_good", {"status": "failed", "reason": "missing"}
+ ),
+ "http_rrc_servers_good": web_results.get(
+ "http_rrc_servers_good",
+ {"status": "failed", "reason": "missing"},
+ ),
+ "plugins_runtime_good": plugins_runtime_result,
"websocket_good": web_results.get(
"websocket_good", {"status": "failed", "reason": "missing"}
),

diff --git a/meshchatx/src/backend/self_check.py b/meshchatx/src/backend/self_check.py
index 6cebe75d..de43224f 100644
--- a/meshchatx/src/backend/self_check.py
+++ b/meshchatx/src/backend/self_check.py
@@ -62,6 +62,13 @@ SELF_CHECK_LABELS = {
"http_identities_good": "HTTP Identities ",
"http_favourites_good": "HTTP Favourites ",
"http_telephone_good": "HTTP Telephone Status ",
+ "http_plugins_good": "HTTP Plugins API ",
+ "http_plugins_trust_good": "HTTP Plugin Trust List ",
+ "http_sideband_plugins_good": "HTTP Sideband Plugins ",
+ "http_sideband_config_good": "HTTP Sideband Config ",
+ "http_rrc_hubs_good": "HTTP RRC Hubs ",
+ "http_rrc_servers_good": "HTTP RRC Servers ",
+ "plugins_runtime_good": "Plugin Manager Runtime ",
"websocket_good": "WebSocket /ws ",
"websocket_rns_link_good": "WebSocket RNS Link API ",
"bots_lifecycle": "Bot Create/Start/Stop ",
@@ -674,11 +681,48 @@ _WEB_PROBE_KEYS = (
"http_identities_good",
"http_favourites_good",
"http_telephone_good",
+ "http_plugins_good",
+ "http_plugins_trust_good",
+ "http_sideband_plugins_good",
+ "http_sideband_config_good",
+ "http_rrc_hubs_good",
+ "http_rrc_servers_good",
"websocket_good",
"websocket_rns_link_good",
)
+def check_plugins_runtime(app: Any) -> dict[str, str]:
+ """Verify plugin manager is wired and bundled example is available when enabled."""
+ manager = getattr(app, "plugin_manager", None)
+ if manager is None:
+ return _status(False, "plugin_manager is not initialized")
+ try:
+ plugins = manager.list_plugins()
+ except Exception as exc:
+ return _status(False, f"list_plugins failed: {exc}")
+ if not isinstance(plugins, list):
+ return _status(False, "list_plugins did not return a list")
+ plugins_enabled = bool(getattr(app, "plugins_enabled", True))
+ if not plugins_enabled:
+ return _status(True)
+ bundled_id = "com.meshchatx.mesh-observatory"
+ if any(
+ isinstance(item, dict) and item.get("id") == bundled_id for item in plugins
+ ):
+ return _status(True)
+ try:
+ manager.install_bundled_examples()
+ plugins = manager.list_plugins()
+ except Exception as exc:
+ return _status(False, f"install_bundled_examples failed: {exc}")
+ if any(
+ isinstance(item, dict) and item.get("id") == bundled_id for item in plugins
+ ):
+ return _status(True)
+ return _status(False, f"bundled plugin {bundled_id} missing after install")
+
+
async def _probe_rns_link_api(ws: Any, *, timeout: float = 10.0) -> dict[str, str]:
"""Exercise generic rns.link.* handlers without requiring a live mesh peer."""
import asyncio
@@ -895,6 +939,41 @@ async def _run_web_api_probes(app: Any) -> dict[str, dict[str, str]]:
"/api/v1/telephone/status",
require_keys=("enabled",),
)
+ results["http_plugins_good"] = await _probe_json_get(
+ client,
+ "/api/v1/plugins",
+ require_keys=("plugins_enabled",),
+ require_nested=(("plugins", list),),
+ )
+ results["http_plugins_trust_good"] = await _probe_json_get(
+ client,
+ "/api/v1/plugins/trusted-publishers",
+ require_keys=("tampered",),
+ require_nested=(("publishers", list),),
+ )
+ results["http_sideband_plugins_good"] = await _probe_json_get(
+ client,
+ "/api/v1/sideband-plugins",
+ require_nested=(("config", dict), ("plugins", list)),
+ )
+ results["http_sideband_config_good"] = await _probe_json_get(
+ client,
+ "/api/v1/sideband-plugins/config",
+ require_keys=(
+ "service_plugins_enabled",
+ "command_plugins_enabled",
+ ),
+ )
+ results["http_rrc_hubs_good"] = await _probe_json_get(
+ client,
+ "/api/v1/rrc/hubs",
+ require_nested=(("hubs", list),),
+ )
+ results["http_rrc_servers_good"] = await _probe_json_get(
+ client,
+ "/api/v1/rrc/servers",
+ require_nested=(("hubs", list),),
+ )
try:
ws = await asyncio.wait_for(client.ws_connect("/ws"), timeout=15)

diff --git a/meshchatx/src/frontend/components/settings/SettingsPage.vue b/meshchatx/src/frontend/components/settings/SettingsPage.vue
index e1d46614..49f3c1d0 100644
--- a/meshchatx/src/frontend/components/settings/SettingsPage.vue
+++ b/meshchatx/src/frontend/components/settings/SettingsPage.vue
@@ -3347,7 +3347,15 @@ export default {
item("http_identities_good", "selftest.http_identities_good"),
item("http_favourites_good", "selftest.http_favourites_good"),
item("http_telephone_good", "selftest.http_telephone_good"),
+ item("http_plugins_good", "selftest.http_plugins_good"),
+ item("http_plugins_trust_good", "selftest.http_plugins_trust_good"),
+ item("http_sideband_plugins_good", "selftest.http_sideband_plugins_good"),
+ item("http_sideband_config_good", "selftest.http_sideband_config_good"),
+ item("http_rrc_hubs_good", "selftest.http_rrc_hubs_good"),
+ item("http_rrc_servers_good", "selftest.http_rrc_servers_good"),
+ item("plugins_runtime_good", "selftest.plugins_runtime_good"),
item("websocket_good", "selftest.websocket_good"),
+ item("websocket_rns_link_good", "selftest.websocket_rns_link_good"),
item("bots_lifecycle", "selftest.bots_lifecycle"),
];
},
@@ -3625,7 +3633,15 @@ export default {
http_identities_good: { ...failed },
http_favourites_good: { ...failed },
http_telephone_good: { ...failed },
+ http_plugins_good: { ...failed },
+ http_plugins_trust_good: { ...failed },
+ http_sideband_plugins_good: { ...failed },
+ http_sideband_config_good: { ...failed },
+ http_rrc_hubs_good: { ...failed },
+ http_rrc_servers_good: { ...failed },
+ plugins_runtime_good: { ...failed },
websocket_good: { ...failed },
+ websocket_rns_link_good: { ...failed },
bots_lifecycle: { ...failed },
};
} finally {

diff --git a/tests/backend/api_json_contract_schemas.py b/tests/backend/api_json_contract_schemas.py
index fecd940b..145c21c0 100644
--- a/tests/backend/api_json_contract_schemas.py
+++ b/tests/backend/api_json_contract_schemas.py
@@ -236,6 +236,13 @@ SELF_TEST_SCHEMA: dict = {
"http_identities_good",
"http_favourites_good",
"http_telephone_good",
+ "http_plugins_good",
+ "http_plugins_trust_good",
+ "http_sideband_plugins_good",
+ "http_sideband_config_good",
+ "http_rrc_hubs_good",
+ "http_rrc_servers_good",
+ "plugins_runtime_good",
"websocket_good",
"websocket_rns_link_good",
"bots_lifecycle",
@@ -271,6 +278,13 @@ SELF_TEST_SCHEMA: dict = {
"http_identities_good": SELF_TEST_STATUS_ITEM_SCHEMA,
"http_favourites_good": SELF_TEST_STATUS_ITEM_SCHEMA,
"http_telephone_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "http_plugins_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "http_plugins_trust_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "http_sideband_plugins_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "http_sideband_config_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "http_rrc_hubs_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "http_rrc_servers_good": SELF_TEST_STATUS_ITEM_SCHEMA,
+ "plugins_runtime_good": SELF_TEST_STATUS_ITEM_SCHEMA,
"websocket_good": SELF_TEST_STATUS_ITEM_SCHEMA,
"websocket_rns_link_good": SELF_TEST_STATUS_ITEM_SCHEMA,
"bots_lifecycle": SELF_TEST_STATUS_ITEM_SCHEMA,

diff --git a/tests/backend/test_self_check.py b/tests/backend/test_self_check.py
index ec6665e0..3c6b3c9c 100644
--- a/tests/backend/test_self_check.py
+++ b/tests/backend/test_self_check.py
@@ -131,6 +131,11 @@ def test_check_bot_launcher_ok():
assert self_check.check_bot_launcher()["status"] == "ok"
+def test_check_plugins_runtime_ok(mock_app):
+ result = self_check.check_plugins_runtime(mock_app)
+ assert result["status"] == "ok", result.get("reason")
+
+
def test_check_web_stack_ok(mock_app, require_loopback_tcp):
from unittest.mock import AsyncMock, MagicMock

diff --git a/tests/e2e/smoke.spec.js b/tests/e2e/smoke.spec.js
index 75677829..57b608e1 100644
--- a/tests/e2e/smoke.spec.js
+++ b/tests/e2e/smoke.spec.js
@@ -46,6 +46,13 @@ test.describe("MeshChatX E2E (Vite + Python backend)", () => {
"http_identities_good",
"http_favourites_good",
"http_telephone_good",
+ "http_plugins_good",
+ "http_plugins_trust_good",
+ "http_sideband_plugins_good",
+ "http_sideband_config_good",
+ "http_rrc_hubs_good",
+ "http_rrc_servers_good",
+ "plugins_runtime_good",
"websocket_good",
"websocket_rns_link_good",
"bots_lifecycle",

diff --git a/tests/frontend/behaviorContracts.test.js b/tests/frontend/behaviorContracts.test.js
index ac79cdd8..4d24ecf0 100644
--- a/tests/frontend/behaviorContracts.test.js
+++ b/tests/frontend/behaviorContracts.test.js
@@ -205,6 +205,12 @@ describe("behavior contracts: RNS Link API", () => {
const selfCheck = readSource("meshchatx/src/backend/self_check.py");
expect(selfCheck).toContain("websocket_rns_link_good");
expect(selfCheck).toContain("_probe_rns_link_api");
+ expect(selfCheck).toContain("http_plugins_good");
+ expect(selfCheck).toContain("http_sideband_plugins_good");
+ expect(selfCheck).toContain("http_rrc_hubs_good");
+ expect(selfCheck).toContain("http_rrc_servers_good");
+ expect(selfCheck).toContain("plugins_runtime_good");
+ expect(selfCheck).toContain("check_plugins_runtime");
const guard = readSource("meshchatx/src/backend/websocket_config_guard.py");
expect(guard).toContain("rns.link.open");
expect(guard).toContain("rns.link.close");


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────